﻿Shader "Custom/Interactive Grass"
{
    Properties
    {
        _TopColor("Top Color", Color) = (1,1,1,1)
        _BottomColor("Bottom Color", Color) = (1,1,1,1)

        _BendRotationRandom("Bend Rotation Random", Range(0, 1)) = 0.2

        _BladeWidth("Blade Width", Float) = 0.05
        _BladeWidthRandom("Blade Width Random", Float) = 0.02
        _BladeHeight("Blade Height", Float) = 0.5
        _BladeHeightRandom("Blade Height Random", Float) = 0.3

        _TessellationUniform("Tessellation Uniform", Range(1, 64)) = 1

        _WindDistortionMap("Wind Distortion Map", 2D) = "white" {}
        _WindFrequency("Wind Frequency", Vector) = (0.05, 0.05, 0, 0)
        _WindStrength("Wind Strength", Float) = 1

        _BladeForward("Blade Forward Amount", Float) = 0.38
        _BladeCurve("Blade Curvature Amount", Range(1, 4)) = 2

        _TranslucentGain("Translucent Gain", Range(0, 1)) = 0.5
    }

    CGINCLUDE

    #include "Lighting.cginc"
    #include "CustomTessellation.cginc"

    #define BLADE_SEGMENTS 3

    float _BendRotationRandom;

    float _BladeHeight;
    float _BladeHeightRandom;
    float _BladeWidth;
    float _BladeWidthRandom;

    sampler2D _WindDistortionMap;
    float4 _WindDistortionMap_ST;

    float2 _WindFrequency;
    float _WindStrength;

    float _BladeForward;
    float _BladeCurve;

    sampler2D _EffectRT;
    float _OrthographicCamSize;
    float3 _CamPos;

    struct geometryOutput
    {
        float4 pos : SV_POSITION;
        float3 normal : NORMAL;
        float2 uv : TEXCOORD0;
        // Used for SHADOW_ATTENUATION
        unityShadowCoord4 _ShadowCoord : TEXCOORD1;
    };

    geometryOutput VertexOutput(float3 pos, float2 uv, float3 normal)
    {
        geometryOutput o;
        o.pos = UnityObjectToClipPos(pos);
        o.normal = UnityObjectToWorldNormal(normal);
        o.uv = uv;
        o._ShadowCoord = ComputeScreenPos(o.pos);

#if UNITY_PASS_SHADOWCASTER
        // Applying the bias prevents artifacts from appearing on the surface.
        o.pos = UnityApplyLinearShadowBias(o.pos);
#endif
        return o;
    }

    // Simple noise function, sourced from http://answers.unity.com/answers/624136/view.html
    // Extended discussion on this function can be found at the following link:
    // https://forum.unity.com/threads/am-i-over-complicating-this-random-function.454887/#post-2949326
    // Returns a number in the 0...1 range.
    float rand(float3 co)
    {
        return frac(sin(dot(co.xyz, float3(12.9898, 78.233, 53.539))) * 43758.5453);
    }

    float3x3 AngleAxis3x3(float angle, float3 axis)
    {
        float c, s;
        sincos(angle, s, c);

        float t = 1 - c;
        float x = axis.x;
        float y = axis.y;
        float z = axis.z;

        return float3x3(
            t * x * x + c, t * x * y - s * z, t * x * z + s * y,
            t * x * y + s * z, t * y * y + c, t * y * z - s * x,
            t * x * z - s * y, t * y * z + s * x, t * z * z + c
            );
    }
    
    geometryOutput GenerateGrassVertex(float3 vertexPosition, float width, float height, float forward, float2 uv, float3x3 transformMatrix)
    {
        float3 tangentPoint = float3(width, forward, height);

        float3 tangentNormal = float3(0, -1, 0);
        float3 localNormal = mul(transformMatrix, tangentNormal);

        float3 localPosition = vertexPosition + mul(transformMatrix, tangentPoint);
        return VertexOutput(localPosition, uv, localNormal);
    }

    [maxvertexcount(BLADE_SEGMENTS * 2 + 1)]
    void geo(triangle vertexOutput IN[3], inout TriangleStream<geometryOutput> triStream)
    {
        float3 pos = IN[0].vertex;

        float3 worldPos = mul(unity_ObjectToWorld, pos);
        float2 rtUV = worldPos.xz - _CamPos.xz;
        rtUV = rtUV / (_OrthographicCamSize * 2);
        rtUV += 0.5;

        float4 clr = tex2Dlod(_EffectRT, float4(rtUV, 0, 0));
        if (clr.b == 1 && clr.a == 1) return;

        float3 vNormal = IN[0].normal;
        float4 vTangent = IN[0].tangent;
        float3 vBinormal = cross(vNormal, vTangent) * vTangent.w;

        float3x3 tangentToLocal = float3x3(
            vTangent.x, vBinormal.x, vNormal.x,
            vTangent.y, vBinormal.y, vNormal.y,
            vTangent.z, vBinormal.z, vNormal.z
            );

        float3x3 facingRotationMatrix = AngleAxis3x3(rand(pos) * UNITY_TWO_PI, float3(0, 0, 1));
        float3x3 bendRotationMatrix = AngleAxis3x3(rand(pos.zzx) * _BendRotationRandom * UNITY_PI * 0.5, float3(-1, 0, 0));

        float2 uv = pos.xz * _WindDistortionMap_ST.xy + _WindDistortionMap_ST.zw + _WindFrequency * _Time.y;
        float2 windSample = (tex2Dlod(_WindDistortionMap, float4(uv, 0, 0)).xy * 2 - 1) * _WindStrength;
        float3 wind = normalize(float3(windSample.x, windSample.y, 0));
        float3x3 windRotation = AngleAxis3x3(UNITY_PI * windSample, wind);

        float3x3 transformationMatrix = mul(mul(mul(tangentToLocal, windRotation), facingRotationMatrix), bendRotationMatrix);
        float3x3 transformationMatrixFacing = mul(tangentToLocal, facingRotationMatrix);

        float height = (rand(pos.zyx) * 2 - 1) * _BladeHeightRandom + _BladeHeight;
        float width = (rand(pos.xzy) * 2 - 1) * _BladeWidthRandom + _BladeWidth;
        height = height * (1 - clr.a);

        float forward = rand(pos.yyz) * _BladeForward;

        for (int i = 0; i < BLADE_SEGMENTS; i++)
        {
            float t = i / (float)BLADE_SEGMENTS;

            float segmentHeight = height * t;
            float segmentWidth = width * (1 - t);

            float segmentForward = pow(t, _BladeCurve) * forward;

            float3x3 transformMatrix = (i == 0 ? transformationMatrixFacing : transformationMatrix);

            triStream.Append(GenerateGrassVertex(pos, segmentWidth, segmentHeight, segmentForward, float2(0, t), transformMatrix));
            triStream.Append(GenerateGrassVertex(pos, -segmentWidth, segmentHeight, segmentForward, float2(1, t), transformMatrix));
        }
        triStream.Append(GenerateGrassVertex(pos, 0, height, forward, float2(0.5, 1), transformationMatrix));
    }

    ENDCG

    SubShader
    {
        Cull Off

        Pass
        {
            Tags
            {
                "RenderType" = "Opaque"
                "LightMode" = "ForwardBase"
            }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma hull hull
            #pragma domain domain
            #pragma geometry geo
            #pragma target 4.6
            #pragma multi_compile_fwdbase

            #include "AutoLight.cginc"

            fixed4 _TopColor;
            fixed4 _BottomColor;

            float _TranslucentGain;

            float4 frag(geometryOutput i, fixed facing : VFACE) : SV_Target
            {
                // 因为当前的Pass关闭了背面剔除，为了确保法线朝向正确的方向，需要根据是否为背面调整法线的方向
                // facing大于0表示正对视角，小于0表示背对视角
                float3 normal = facing > 0 ? i.normal : -i.normal;
                // 得到阴影纹理中的值
                float shadow = SHADOW_ATTENUATION(i);
                // 结合阴影计算法线方向和世界空间中平行光方向的点乘结果
                float NdotL = saturate(saturate(dot(normal, _WorldSpaceLightPos0)) + _TranslucentGain)
                                       * shadow;

                // 内置方法计算环境光
                float3 ambient = ShadeSH9(float4(normal, 1));
                // 漫反射+环境光，得到光照强度
                float4 lightIntensity = NdotL * _LightColor0 + float4(ambient, 1);
                // 将光照反射与小草叶片顶部颜色结合，再与底部的颜色通过lerp得到最终颜色
                return lerp(_BottomColor, _TopColor * lightIntensity, i.uv.y);
            }

            ENDCG
        }

        Pass
        {
	        Tags
	        {
		        "LightMode" = "ShadowCaster"
	        }

	        CGPROGRAM

	        #pragma vertex vert
	        #pragma geometry geo
	        #pragma fragment frag
	        #pragma hull hull
	        #pragma domain domain
	        #pragma target 4.6
	        #pragma multi_compile_shadowcaster

	        float4 frag(geometryOutput i) : SV_Target
	        {
		        SHADOW_CASTER_FRAGMENT(i)
	        }

	        ENDCG
        }
    }
}